﻿using TPXSDK.NET.Share;
using static TPXSDK.NET.GIS.AbsPos;

namespace TPXSDK.NET.GIS;

/// <summary>
/// 地理信息工具类
/// </summary>
public static class GeoUtils
{
    /// <summary>
    /// 计算两个点中间位置
    /// </summary>
    /// <returns></returns>
    public static AbsPos GetMiddlePos(AbsPos p1, AbsPos p2)
    {
        var bearing = p1.Bearing(p2);
        var dist = p1.Distance(p2) / 2.0;
        var ret = p1.LocUpdate(bearing, dist);
        ret.Alt = p1.Alt + (p2.Alt - p1.Alt) / 2;
        return ret;
    }


    /// <summary>
    /// 获取多边形中心点(包含直线)
    /// </summary>
    public static AbsPos GetCentroid(IList<AbsPos> polygon)
    {
        if (polygon.Count < 2)
        {
            return Zero;
        }

        if (polygon.Count == 2)
        {
            return GetMiddlePos(polygon[0], polygon[1]);
        }

        var refLoc = polygon[0];
        var relPts = new List<RelPos>(polygon.Count);
        for (int i = 0; i < polygon.Count; i++)
        {
            var relPos = polygon[i].LocDiff(refLoc);
            relPts.Add(relPos);
        }
        var c = GetCentroid(relPts);
        return refLoc.LocOffset(c.X, c.Y);
    }

    /// <summary>
    /// 获取多边形中心点
    /// </summary>
    public static RelPos GetCentroid(IList<RelPos> pts)
    {
        if (pts.Count < 3) return RelPos.Zero;

        double area = 0;
        double centroidX = 0;
        double centroidY = 0;

        for (int i = 0; i < pts.Count; i++)
        {
            var p1 = pts[i];
            var p2 = pts[(i + 1) % pts.Count]; // 循环到第一个点

            double term = p1.X * p2.Y - p2.X * p1.Y;
            area += term;

            centroidX += (p1.X + p2.X) * term;
            centroidY += (p1.Y + p2.Y) * term;
        }

        area *= 0.5;
        centroidX /= (6 * area);
        centroidY /= (6 * area);

        return new RelPos(centroidX, centroidY);
    }

    /// <summary>
    /// 计算点loc到P1-P2构成的线段垂直距离
    /// </summary>
    public static double DistToLine(AbsPos loc, AbsPos p1, AbsPos p2)
    {
        var distToEnd = loc.Distance(p2);
        /* Return error if arguments are bad */
        if (distToEnd < 0.001f) return 0;
        var bearingEnd = DataUtils.Deg2Rad(loc.Bearing(p2));
        var bearingTrack = DataUtils.Deg2Rad(p1.Bearing(p2));
        var bearingDiff = DataUtils.WrapPI(bearingTrack - bearingEnd);
        var sinBearingDiff = Math.Sin(bearingDiff);
        return Math.Abs(distToEnd * sinBearingDiff);
    }

    /// <summary>
    /// 检查纬度值是否有效
    /// </summary>
    public static bool IsValidLat(double lat)
    {
        return !(lat > 90 || lat < -90 || Math.Abs(lat) < 0.00000000001);
    }

    /// <summary>
    /// 检查经度值是否有效
    /// </summary>
    public static bool IsValidLng(double lon)
    {
        return !(lon > 180 || lon < -180 || Math.Abs(lon) < 0.00000000001);
    }

    /// <summary>
    /// 生成圆圈点集合
    /// </summary>
    public static AbsPos[] GenCirclePoints(AbsPos center, double radius, int maxCnt = 80)
    {
        if (maxCnt < 80) maxCnt = 80;
        var result = new List<AbsPos>();
        var stepAng = 360.0 / maxCnt;
        for (var i = 0; i < maxCnt; i++)
        {
            var newLoc = center.LocUpdate(stepAng*i, radius);
            result.Add(newLoc);
        }
        return [.. result];
    }

    /// <summary>
    /// 将十进制度格式转换为度分秒格式
    /// </summary>
    public static (int d, int m, double s) DDtoDMS(double dd)
    {
        int degrees = (int)dd;
        double decimalPart = Math.Abs(dd - degrees);
        int minutes = (int)(decimalPart * 60);
        double seconds = (decimalPart * 60 - minutes) * 60;
        return (degrees, minutes, seconds);
    }

    /// <summary>
    /// 将十进制度格式转换为度分格式
    /// </summary>
    public static (int d, double m) DDtoDM(double dd)
    {
        int degrees = (int)dd;
        double decimalPart = Math.Abs(dd - degrees);
        double minutes = decimalPart * 60;
        return (degrees, minutes);
    }

    /// <summary>
    /// 将度分秒格式转换为十进制度格式
    /// </summary>
    public static double DMSToDD(int degrees, int minutes, double seconds)
    {
        double dd = degrees + (minutes / 60.0) + (seconds / 3600.0);
        return dd;
    }

    /// <summary>
    /// 将度分格式转换为十进制度格式
    /// </summary>
    public static double DMToDD(int degrees, double minutes)
    {
        double dd = degrees + (minutes / 60.0);
        return dd;
    }
}
